# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: helpers\mailer\monthly\__init__.py
__author__ = "Attila Gerendi (Sanyi)"
import logging, time, html, datetime
from dateutil.tz import tzutc
from dateutil.relativedelta import relativedelta
from sqlalchemy import select as sql_select
from sqlalchemy import or_ as sql_or
from sqlalchemy import and_ as sql_and
from sqlalchemy import distinct as sql_distinct
from sqlalchemy import func as sql_func
from db import Connection
from db.tables.users import UserRow, UsersTable
from db.tables.monthly_emails import MonthlyEmailRow, MonthlyEmailsTable
from db.tables.events import EventRow
from db.tables.licenses import LicenseRow, LicensesTable, LicenseStatus
from db.tables.target_vulns import TargetVulnRow
from db.views.targets_extended import TargetExtendedViewRow
from helpers.mailer.renderer.jinja import JinjaRenderer
from settings_provider import settings
from helpers.setup_types import (
    is_setup_type_aws,
    is_setup_type_on_premise_master,
    get_setup_type,
)

logger = logging.getLogger("helpers.mailer.monthly")
year2k = datetime.datetime(year=2000, month=1, day=1, tzinfo=tzutc())


class MonthlyMailer:
    def __init__(
        self,
        generator,
        mailer,
        shard="master",
        from_address="Acunetix Online <onlinescanner@acunetix.com>",
        cadence=0.5,
    ):
        self.from_address = from_address
        self.mailer = mailer
        self.shard = shard
        self.generator = generator
        self.should_stop = False
        self.latest_blog_posts = []
        self.latest_vulnerabilities = []
        self.renderer = JinjaRenderer(
            settings.get("email_templates"), trim_blocks=True, lstrip_blocks=True
        )
        self.cadence = cadence

    def stop(self):
        logger.info("shutting down")
        self.should_stop = True

    @staticmethod
    def get_latest_blog_posts(link_count=5):
        import feedparser

        result = []
        feed = feedparser.parse("http://www.acunetix.com/blog/feed/atom/")
        for i in range(0, min(link_count, len(feed["entries"]))):
            result.append(
                dict(
                    url=feed["entries"][i]["links"][0]["href"],
                    title=html.unescape(feed["entries"][i]["title_detail"]["value"]),
                )
            )

        return result

    @staticmethod
    def get_latest_vulnerabilities():
        return []

    @staticmethod
    def on_premise_gen(shard="master"):
        current_datetime = datetime.datetime.now(tzutc())
        sf = UsersTable.outerjoin(
            MonthlyEmailsTable, MonthlyEmailRow.user_id == UserRow.user_id
        )
        q = (
            sql_select((UserRow, MonthlyEmailRow.last_sent))
            .select_from(sf)
            .where(UserRow.owner_id.is_(None))
            .where(UserRow.deleted_at.is_(None))
            .where(UserRow.notifications["monthly_status"].astext == "true")
            .where(MonthlyEmailRow.disabled.isnot(True))
            .where(
                sql_or(
                    MonthlyEmailRow.last_sent
                    < current_datetime - (relativedelta(months=1)),
                    sql_and(
                        MonthlyEmailRow.last_sent.is_(None),
                        UserRow.created
                        < datetime.datetime.now(tzutc()) - (relativedelta(months=1)),
                    ),
                )
            )
        )
        users = []
        with Connection(shard) as (db):
            for user in db.execute(q).fetchall():
                user = dict(user)
                user["current_datetime"] = current_datetime
                users.append(user)

        for user in users:
            yield user

    @staticmethod
    def online_accounts_gen(
        shard="master", max_emails_per_query=100, first_email_days=14, extra_fields=None
    ):
        last_user_id = None
        while True:
            current_datetime = datetime.datetime.now(tzutc())
            users = []
            sf = UsersTable.outerjoin(
                MonthlyEmailsTable, MonthlyEmailRow.user_id == UserRow.user_id
            )
            sf = sf.join(LicensesTable, UserRow.license_key == LicenseRow.license_key)
            q = (
                sql_select(
                    (
                        UserRow,
                        MonthlyEmailRow.last_sent,
                        LicenseRow.expires.label("license_expires"),
                        LicenseRow.last_refresh.label("license_last_refresh"),
                    )
                )
                .select_from(sf)
                .where(UserRow.license_type == "customer")
                .where(LicenseRow.status == LicenseStatus.OK)
                .where(UserRow.owner_id.is_(None))
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.notifications["monthly_status"].astext == "true")
                .where(MonthlyEmailRow.disabled.isnot(True))
                .where(
                    sql_or(
                        MonthlyEmailRow.last_sent
                        < current_datetime - (relativedelta(months=1)),
                        sql_and(
                            MonthlyEmailRow.last_sent.is_(None),
                            datetime.datetime.now(tzutc())
                            - (relativedelta(days=first_email_days))
                            > UserRow.created,
                        ),
                    )
                )
                .order_by(UserRow.user_id)
                .limit(max_emails_per_query)
            )
            if last_user_id:
                q.where(UserRow.user_id > last_user_id)
            with Connection(shard) as (db):
                for user in db.execute(q).fetchall():
                    user = dict(user)
                    if extra_fields:
                        user.update(extra_fields)
                    user["current_datetime"] = current_datetime
                    users.append(user)

            for user in users:
                yield user

            if len(users) < max_emails_per_query:
                break
            last_user_id = users[-1]["user_id"]

        last_user_id = None
        while True:
            current_datetime = datetime.datetime.now(tzutc())
            users = []
            eq = sql_select((sql_distinct(EventRow.owner_id),)).where(
                EventRow.created < current_datetime - (relativedelta(days=90))
            )
            sf = UsersTable.outerjoin(
                MonthlyEmailsTable, MonthlyEmailRow.user_id == UserRow.user_id
            )
            q = (
                sql_select((UserRow, MonthlyEmailRow.last_sent))
                .select_from(sf)
                .where(UserRow.license_type == "trial")
                .where(UserRow.owner_id.is_(None))
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.notifications["monthly_status"].astext == "true")
                .where(UserRow.user_id.in_(eq))
                .where(MonthlyEmailRow.disabled.isnot(True))
                .where(
                    sql_or(
                        MonthlyEmailRow.last_sent
                        < current_datetime - (relativedelta(months=1)),
                        sql_and(
                            MonthlyEmailRow.last_sent.is_(None),
                            datetime.datetime.now(tzutc())
                            - (relativedelta(days=first_email_days))
                            > UserRow.created,
                        ),
                    )
                )
                .order_by(UserRow.user_id)
                .limit(max_emails_per_query)
            )
            if last_user_id:
                q.where(UserRow.user_id > last_user_id)
            with Connection(shard) as (db):
                for user in db.execute(q).fetchall():
                    user = dict(user)
                    if extra_fields:
                        user.update(extra_fields)
                    user["current_datetime"] = current_datetime
                    users.append(user)

            for user in users:
                yield user

            if len(users) < max_emails_per_query:
                break
            last_user_id = users[-1]["user_id"]

    def process_email_data(self, data):
        logger.info("processing: %s", data["email"])
        data = dict(data)
        data["latest_blog_posts"] = self.latest_blog_posts
        data["latest_vulnerabilities"] = self.latest_vulnerabilities
        relative_date = data["current_datetime"] - (relativedelta(days=30))
        data["relative_timedelta_days"] = 30
        data["support_contact"] = dict(
            url="mailto:support@acunetix.com", title="support@acunetix.com"
        )
        data["setup_type"] = settings.get("setup_type")
        data["frontend_url"] = is_setup_type_aws() and "https://online.acunetix.com/"
        if data["license_type"] == "customer":
            if data["license_last_refresh"] + (
                relativedelta(days=1)
            ) < datetime.datetime.now(tzutc()):
                from db.data.users import update_license

                license_data = update_license(
                    data["license_key"], self.shard, blocking_timeout=30
                )
                if license_data["status"] != LicenseStatus.OK:
                    return
            else:
                data["frontend_url"] = settings["frontend_url"] + "/"
            with Connection(self.shard) as (db):
                q = (
                    sql_select(
                        (
                            TargetExtendedViewRow.target_id,
                            TargetExtendedViewRow.address,
                            TargetExtendedViewRow.description,
                            TargetExtendedViewRow.last_scan_date,
                            TargetExtendedViewRow.threat,
                            TargetExtendedViewRow.vuln_info,
                            TargetExtendedViewRow.vuln_medium,
                            TargetExtendedViewRow.vuln_low,
                            TargetExtendedViewRow.vuln_high,
                        )
                    )
                    .where(TargetExtendedViewRow.owner_id == data["user_id"])
                    .where(TargetExtendedViewRow.deleted_at.is_(None))
                )
                targets = dict()
                data["targets_scanned"] = 0
                data["targets_scanned_period"] = 0
                targets_not_scanned = []
                last_scan_date = year2k
                for target in db.execute(q).fetchall():
                    target = dict(target)
                    targets[target["target_id"]] = target
                    if target["last_scan_date"]:
                        if target["last_scan_date"] > last_scan_date:
                            last_scan_date = target["last_scan_date"]
                        data["targets_scanned"] += 1
                        if target["last_scan_date"] > relative_date:
                            data["targets_scanned_period"] += 1
                            continue
                    targets_not_scanned.append(target["target_id"])

                data["targets_not_scanned"] = targets_not_scanned
                if last_scan_date == year2k:
                    last_scan_date = None
                data["last_scan_date"] = last_scan_date
                if last_scan_date:
                    if data["targets_scanned_period"] == 0:
                        if data["targets_scanned"]:
                            data["warning_last_scan_days"] = (
                                data["current_datetime"] - last_scan_date
                            ).days
                data["targets"] = targets
                data["target_count"] = len(targets)
                data["vulns_open"] = dict(info=0, low=0, med=0, high=0)
                if targets:
                    q = (
                        sql_select(
                            (
                                sql_func.count(TargetVulnRow.severity).label("c"),
                                TargetVulnRow.severity,
                            )
                        )
                        .where(TargetVulnRow.target_id.in_(targets.keys()))
                        .where(TargetVulnRow.status == "open")
                        .group_by(TargetVulnRow.severity)
                    )
                    for row in db.execute(q).fetchall():
                        if row.severity == 0:
                            data["vulns_open"]["info"] = row.c
                        else:
                            if row.severity == 1:
                                data["vulns_open"]["low"] = row.c
                            else:
                                if row.severity == 2:
                                    data["vulns_open"]["med"] = row.c
                                else:
                                    if row.severity == 3:
                                        data["vulns_open"]["high"] = row.c

                data["vulns_detected_period"] = dict(info=0, low=0, med=0, high=0)
                if targets:
                    q = (
                        sql_select(
                            (
                                sql_func.count(TargetVulnRow.severity).label("c"),
                                TargetVulnRow.severity,
                            )
                        )
                        .where(TargetVulnRow.target_id.in_(targets.keys()))
                        .where(TargetVulnRow.status == "fixed")
                        .where(TargetVulnRow.last_seen > relative_date)
                        .group_by(TargetVulnRow.severity)
                    )
                    for row in db.execute(q).fetchall():
                        if row.severity == 0:
                            data["vulns_detected_period"]["info"] = row.c
                        else:
                            if row.severity == 1:
                                data["vulns_detected_period"]["low"] = row.c
                            else:
                                if row.severity == 2:
                                    data["vulns_detected_period"]["med"] = row.c
                                else:
                                    if row.severity == 3:
                                        data["vulns_detected_period"]["high"] = row.c

                data["vulns_fixed_period"] = dict(info=0, low=0, med=0, high=0)
                if targets:
                    q = (
                        sql_select(
                            (
                                sql_func.count(TargetVulnRow.severity).label("c"),
                                TargetVulnRow.severity,
                            )
                        )
                        .where(TargetVulnRow.target_id.in_(targets.keys()))
                        .where(TargetVulnRow.status == "fixed")
                        .where(TargetVulnRow.fixed_at > relative_date)
                        .group_by(TargetVulnRow.severity)
                    )
                    for row in db.execute(q).fetchall():
                        if row.severity == 0:
                            data["vulns_fixed_period"]["info"] = row.c
                        else:
                            if row.severity == 1:
                                data["vulns_fixed_period"]["low"] = row.c
                            else:
                                if row.severity == 2:
                                    data["vulns_fixed_period"]["med"] = row.c
                                else:
                                    if row.severity == 3:
                                        data["vulns_fixed_period"]["high"] = row.c

            data["setup_type"] = get_setup_type()
            subject = self.renderer.render(data, "monthly.subject.txt.jinja2")
            body_html = self.renderer.render(data, "monthly.html.jinja2")
            body_txt = self.renderer.render(data, "monthly.txt.jinja2")
            self.mailer.send(
                destination=data["email"],
                from_address=self.from_address,
                subject=subject,
                body_html=body_html,
                body_txt=body_txt,
                timeout=100,
            )
            if data["last_sent"]:
                if (
                    data["current_datetime"] - (relativedelta(days=1))
                    < data["last_sent"]
                    < data["current_datetime"] + (relativedelta(days=1))
                ):
                    last_sent = data["last_sent"] + (relativedelta(months=1))
                last_sent = data["current_datetime"]
            qu = (
                (MonthlyEmailsTable.update(values=dict(last_sent=last_sent)))
                .where(MonthlyEmailRow.user_id == data["user_id"])
                .returning(MonthlyEmailRow.user_id)
            )
            qi = MonthlyEmailsTable.insert().values(
                user_id=data["user_id"], last_sent=last_sent
            )
            with Connection(self.shard) as (db):
                if not db.execute(qu).scalar():
                    db.execute(qi)

    def run(self):
        if is_setup_type_on_premise_master():
            q = (UsersTable.update(values=dict(created=sql_func.now()))).where(
                UserRow.created.is_(None)
            )
            with Connection(self.shard) as (db):
                db.execute(q)
        while not self.should_stop:
            self.latest_vulnerabilities = self.get_latest_vulnerabilities()
            try:
                if self.mailer.can_send():
                    for data in self.generator():
                        self.process_email_data(data)
                        if self.should_stop:
                            break
                        for i in range(0, int(10 / self.cadence)):
                            if self.should_stop:
                                break
                            time.sleep(0.1)

            except Exception as e:
                logger.exception("errors in main %s", e)

            for i in range(0, 600):
                if self.should_stop:
                    break
                time.sleep(0.1)

        logger.info("bye.")
